/*******************************************************************************
 *      CSE 531 – Distributed and Multiprocessor Operating Systems
 *
 *           Something of a scheduler for our fake threads.
 *
 *   Author: Denis Ryndine
 *   Email:  dryndine@asu.edu
 *   Date:   2010-11-06
 *
 *   main.c: 
 *       Test if the fake sch531 scheduler ticks our fake threads.
 *
 ******************************************************************************/

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include "531-scheduler.h"

/* --- What your threads would wrap --- */
void 
thread_yield(void)
{
  sch531_thread_yield();
}
void 
thread_sleep(unsigned secs)
{
  sch531_thread_sleep(secs);
}
void 
thread_enter_csection(void)
{
  sch531_lock_context();
}  
void 
thread_exit_csecion(void)
{
  sch531_unlock_context();
}
void
thread_got_blocked(void)
{
  sch531_thread_blocked();
}
/* --------------------------------------- */


#include <ucontext.h>
#include <string.h>
#define ARR_LEN(a) (sizeof(a)/sizeof(a[0]))

/* This is an ugly version of what you did with TCB Q & threads for the first
2 projects (since I can't just paste my solution to those projects)*/
static ucontext_t  ctxts[4];
static ucontext_t* runQ[4]={NULL}; //to use later in example
static unsigned currQ=0;
#define Q_NEXT() (currQ = (currQ+1) % ARR_LEN(runQ))

/* Suppose you have something thread can block on (can be a semaphore q)*/
struct block
{
  ucontext_t* thr;
  unsigned thrid;
}block = {NULL, 0};

/* Threads will block themselves on this just for fun. But same way they can
block on P() of a sem.*/
void
block_thread(unsigned id)
{
  if (id >= ARR_LEN(runQ))
    return;
  /* Cannot be pre-empted now*/
  thread_enter_csection();
  if (block.thr != NULL)
  {
    runQ[block.thrid] = block.thr;
  }
  block.thr   = runQ[id];
  runQ[id]    = NULL;
  block.thrid = id;
  /*adjust run q to next context for scheduler to switch to */
  Q_NEXT();
  /* siginal we are blocked, csection will be unlocked before contextswitch */
  thread_got_blocked();
}

/*  ----------- Implement funcs scheduler requires  -------------- */
ucontext_t* 
threads_rotate_context_run_Q( void )
{
  while(1)
  {
    Q_NEXT();
    if (NULL != runQ[currQ])
      return runQ[currQ];
  }
}
ucontext_t*
threads_get_first_context_run_Q( void )
{
  return runQ[currQ];
}

ucontext_t*
threads_get_blocked_context()
{
  return block.thr;
}
/* ----------------------------------------------------------------- */

/* This has your tcb init and start thread combined here. Note now allowing to 
pass args to thread func. */
void 
start_thread(void (*function)(void *), void* args)
{
  void* stack = malloc( 8192 );
  assert(stack);
  memset(&ctxts[currQ], '\0', sizeof ctxts[0]);
  getcontext(&ctxts[currQ]);
  ctxts[currQ].uc_stack.ss_sp = stack;
  ctxts[currQ].uc_stack.ss_size = 8192;
  makecontext(&ctxts[currQ], (void*)function, 1, args);
  runQ[currQ] = &ctxts[currQ];
  Q_NEXT();
}

/* To override (or conditional compile) your run() in threads.*/
void 
run(void)
{   
  if (0 != sch531_init())
  {  
    fprintf(stderr, "threads: failed to init scheduler. Bail...\n");
    return;
  }
  /////// 
  sch531_run(); 
  //////should not get here
  fprintf(stderr, "run returned.\n");
  assert(0);
}

/* To pass args to thread func*/
typedef struct thrargs
{
  unsigned delay;
}thrargs;
unsigned globID;
/* some dummy func */
void 
func1(void *args)
{
  assert(args);
  thrargs* myargs = args;
  unsigned mid=globID++;
  
  while(1)
  {
    printf("thr[%u]: sleep for %u ...\n", mid, myargs->delay);
    thread_sleep(myargs->delay);
  }
}

void func2(void* args)
{
  assert(args);
  thrargs* myargs = args;
  unsigned mid=globID++;
  unsigned cntr=0;
  
  while(1)
  {
    printf("thr[%u]: sleep for %u ...\n", mid, myargs->delay);
    thread_sleep(myargs->delay);
    ++cntr;
    if (cntr == 4)
    {
      printf("thr[%u]:\t ---> gonna block ..\n", mid);
      //manipulate run q
      block_thread(mid);
      printf("thr[%u]:\t <--- got un-blocked..\n", mid);
      cntr=0;
    }
  }
}

int
main(int argc, char* argv[])
{
  thrargs t1={2};
  thrargs t2={5};
  thrargs t3={3};
  thrargs t4={1};

  start_thread(func1, &t4);
  start_thread(func2, &t3);
  start_thread(func2, &t1);
  start_thread(func2, &t2);
  
  run();   
  
  return 0; 
}
